﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace DevelopersToolbox {
    public partial class DiffViewControl : UserControl {
        private DiffViewMode _viewMode;
        private int[] _leftSource;
        private int[] _rightSource;
        private Diff.Item[] _diff;
        private Color _replaceBackColor = Color.Red;
        private Color _replaceColor = Color.White;
        private Color _defaultBackColor = Color.White;
        private Color _defaultColor = Color.Black;
        private Color _changeBackColor = Color.LightGreen;
        private Color _changeColor = Color.Black;
        private Color _addBackColor = Color.LightGray;
        private Color _addColor = Color.LightGray;

        public DiffViewControl() {
            InitializeComponent();
            ViewMode = DiffViewMode.Text;
            _leftText.RichTextBox.ReadOnly = true;
            _rightText.RichTextBox.ReadOnly = true;
        }

        public DiffViewMode ViewMode {
            get { return _viewMode; }
            set {
                if (value == DiffViewMode.Text) {
                    _leftBinary.Visible = false;
                    _rightBinary.Visible = false;
                    _leftText.Visible = true;
                    _rightText.Visible = true;
                } else if (value == DiffViewMode.Binary) {
                    _leftText.Visible = false;
                    _rightText.Visible = false;
                    _leftBinary.Visible = true;
                    _rightBinary.Visible = true;
                }
                _viewMode = value;
                Rebuild();
            }
        }

        public void SetResults(int[] left, int[] right, Diff.Item[] diff) {
            _leftSource = left;
            _rightSource = right;
            _diff = diff;
            Rebuild();
        }

        private void DiffViewControl_Resize(object sender, EventArgs e) {
            int sideWidth = (Width - (_leftText.Padding.Left + _leftText.Padding.Right + _rightText.Padding.Left + _rightText.Padding.Right)) / 2;
            _leftBinary.Width = sideWidth;
            _rightBinary.Width = sideWidth;
            _leftText.Width = sideWidth;
            _rightText.Width = sideWidth;

            _leftBinary.Top = _leftBinary.Padding.Top;
            _rightBinary.Top = _rightBinary.Padding.Top;
            _leftText.Top = _leftText.Padding.Top;
            _rightText.Top = _rightText.Padding.Top;

            _leftBinary.Height = Height - (_leftBinary.Padding.Top + _leftBinary.Padding.Bottom);
            _rightBinary.Height = Height - (_rightBinary.Padding.Top + _rightBinary.Padding.Bottom);
            _leftText.Height = Height - (_leftText.Padding.Top + _leftText.Padding.Bottom);
            _rightText.Height = Height - (_rightText.Padding.Top + _rightText.Padding.Bottom);

            _leftText.Left = _leftText.Padding.Left;
            _leftBinary.Left = _leftBinary.Padding.Left;
            _rightText.Left = _leftText.Right + _leftText.Padding.Right;
            _rightBinary.Left = _leftBinary.Right + _leftBinary.Padding.Right;
        }

        private void Rebuild() {
            if (_leftSource == null || _rightSource == null) {
                return;
            }

            if (ViewMode == DiffViewMode.Binary) {
                RebuildBinary();
            } else if (ViewMode == DiffViewMode.Text) {
                RebuildText();
            }
        }

        private void RebuildText() {
            _leftText.Text = ConvertToString(_leftSource);
            _rightText.Text = ConvertToString(_rightSource);

            // reset formatting
            _leftText.RichTextBox.Select(0, _leftText.RichTextBox.Text.Length);
            _leftText.RichTextBox.SelectionBackColor = _defaultBackColor;
            _leftText.RichTextBox.SelectionColor = _defaultColor;
            _rightText.RichTextBox.Select(0, _rightText.RichTextBox.Text.Length);
            _rightText.RichTextBox.SelectionBackColor = _defaultBackColor;
            _rightText.RichTextBox.SelectionColor = _defaultColor;

            foreach (Diff.Item item in _diff) {
                if (item.deletedA == item.insertedB) {
                    _leftText.RichTextBox.Select(item.StartA, item.deletedA);
                    _leftText.RichTextBox.SelectionBackColor = _replaceBackColor;
                    _leftText.RichTextBox.SelectionColor = _replaceColor;
                    _rightText.RichTextBox.Select(item.StartB, item.insertedB);
                    _rightText.RichTextBox.SelectionBackColor = _replaceBackColor;
                    _rightText.RichTextBox.SelectionColor = _replaceColor;
                }
            }

            foreach (Diff.Item item in _diff) {
                if (item.deletedA != 0 && item.insertedB == 0) {
                    DiffAddToLeft(item);
                } else if (item.deletedA == 0 && item.insertedB != 0) {
                    DiffAddToRight(item);
                } else if (item.deletedA != item.insertedB) {
                    DiffAddToLeftAndRight(item);
                }
            }
        }

        private void DiffAddToLeftAndRight(Diff.Item item) {
            DiffAddToLeft(item);
            item.StartB += item.deletedA;
            item.StartA += item.deletedA;
            DiffAddToRight(item);
        }

        private void DiffAddToRight(Diff.Item item) {
            _rightText.RichTextBox.Select(item.StartA, item.insertedB);
            _rightText.RichTextBox.SelectionBackColor = _changeBackColor;
            _rightText.RichTextBox.SelectionColor = _changeColor;
            _leftText.RichTextBox.Select(item.StartB, 0);
            int len = Math.Min(item.insertedB, _rightText.RichTextBox.Text.Length - item.insertedB);
            _leftText.RichTextBox.SelectedText = ToReplacementText(_rightText.RichTextBox.Text.Substring(item.StartA, len));
            _leftText.RichTextBox.Select(item.StartB, item.insertedB);
            _leftText.RichTextBox.SelectionBackColor = _addBackColor;
            _leftText.RichTextBox.SelectionColor = _addColor;
        }

        private void DiffAddToLeft(Diff.Item item) {
            _leftText.RichTextBox.Select(item.StartB, item.deletedA);
            _leftText.RichTextBox.SelectionBackColor = _changeBackColor;
            _leftText.RichTextBox.SelectionColor = _changeColor;
            _rightText.RichTextBox.Select(item.StartA, 0);
            int len = Math.Min(item.deletedA, _leftText.RichTextBox.Text.Length - item.StartB);
            if (item.StartB < _leftText.RichTextBox.Text.Length) {
                _rightText.RichTextBox.SelectedText = ToReplacementText(_leftText.RichTextBox.Text.Substring(item.StartB, len));
                _rightText.RichTextBox.Select(item.StartA, item.deletedA);
                _rightText.RichTextBox.SelectionBackColor = _addBackColor;
                _rightText.RichTextBox.SelectionColor = _addColor;
            }
        }

        private string ConvertToString(int[] source) {
            char[] result = new char[source.Length];
            for (int i = 0; i < source.Length; i++) {
                result[i] = (char)source[i];
            }
            return new string(result);
        }

        private string ToReplacementText(string s) {
            return Regex.Replace(s, @"\S", " ");
        }

        private void RebuildBinary() {
            _leftBinary.Data = ConvertToByteArray(_leftSource);
            _rightBinary.Data = ConvertToByteArray(_rightSource);
        }

        private byte[] ConvertToByteArray(int[] source) {
            byte[] result = new byte[source.Length];
            for (int i = 0; i < source.Length; i++) {
                result[i] = (byte)source[i];
            }
            return result;
        }
    }

    public enum DiffViewMode {
        Text,
        Binary
    }
}
